Skip to content

feat(lineage): expose the live agent tree (siblings, children, roots) on request.lineage (#457) - #544

Merged
ScriptedAlchemy merged 7 commits into
mainfrom
feat/457-lineage-tree
Sep 4, 2026
Merged

feat(lineage): expose the live agent tree (siblings, children, roots) on request.lineage (#457)#544
ScriptedAlchemy merged 7 commits into
mainfrom
feat/457-lineage-tree

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Fixes #457.

Why

Since #421/#444, (await agent()).lineage answers "who is my parent, what is my root, am I a subagent" for the current request, while the warm runtime's lineage registry holds the whole tree and exposes it only through snapshot() (dumps, the Workbench). A plugin that coordinates agent threads (examples/worktree-proximity) therefore hand-rolled an actor tree — kind: root|child, parentSessionId, status: active|stopped — fed from session/start, agent/start, and stop, duplicating what the registry already knew.

Design

  • AgentLineage.tree?: AgentLineageTree (@agent-bundle/runtime) — { siblings, children, roots } of AgentLineagePeer ({ conversation, depth, parent?, startedAt, subagent?, resolution }). Computed at resolve time from LineageState.nodes by the same registry that placed the request, on every path that produces a placed lineage: observe() for event routes, resolveToolCall() for MCP calls correlated through a hook window or a Codex _meta whose thread the registry saw start. It rides the existing Observed<AgentLineage> axis, so unavailability semantics and the context.lineage harness seam are unchanged, and it is plain frozen data (structuredClone-safe), so the Flight worker receives it exactly as the entry process resolved it.
  • Scope (the proximity model): siblings is every other live conversation under the caller's root, any depth, oldest first — the root itself included for a subagent — so a coordinator sees the whole live tree it belongs to; children the direct live children; roots the other live depth-0 conversations, on Cursor only those seen in the same workspace_roots (the rule that already scopes child binding). A Cursor child whose conversation has not spoken yet is listed under its subagent_id.
  • Provenance — every peer field is a registry fact; each peer's resolution is judged exactly as it would be on a request that node made itself (nodeResolution, extracted from resolve()). Stopped nodes are never listed. No tree when the registry did not place the request: a standalone hook (resolveStandaloneLineage), or a Codex _meta naming a thread the registry never saw start, still resolves its own chain and carries no tree. Nothing derives an actor or a user (Actor principal for generated event scopes: typed unavailable reasons and the one derivable host field (parked from #233/#223) #391); request.lineage stays the only identity-adjacent surface (feat(lineage): request.lineage is the only identity-adjacent surface; scoped, self-correcting Cursor child binding #444).
  • Not changedAGENT_REQUEST_STORE_VERSION (the handle shape is unchanged; tree is an optional field of an existing value), the Workbench replay provenance (a deterministic replay has no registry and never produces a tree), the registry's mutations and journal.

examples/worktree-proximity

Deleted the actor bookkeeping the issue names: ActorSchema (kind, parentSessionId, status, the per-actor provenance.id/parentSessionId), the actorObserved and actorStopped events, and the session:<root> pseudo-id. The state definition is now worktree-proximity/intentbindings (actor → worktree, the one fact the registry cannot know), activities, refusals — with actorBound, actorReleased, intentRecorded, edgeRefused. session/start and agent/start bind conversations under the ids request.lineage.tree lists them by; the edge itself is the registry's. tool/before takes liveness from lineage.tree.siblings (liveConversations()): an intent held by a host-identified actor the tree no longer lists under our root is stale and warns nobody; a derived worktree:<root> actor is never filtered that way; a lineage without a tree presumes nothing. New src/events/agent/stop.tsx route (so the registry marks children stopped and the route releases their binding and intent). The coordinator status tool reports agents — the tree the runtime resolved for its call (agentTree()), honestly unavailable for a client no hook window names — beside bindings, activities, refusals. The agent-topology provider stays an honest stub until #459 (providers still receive no lineage). README rewritten accordingly.

examples/host-test: dump --conversation now matches the request's own chain (conversation/parent/root/subagent.id) instead of a JSON substring, so a sibling listed in tree does not make a record match.

Tests

  • rsc-runtime/tests/lineage-registry.test.ts — new lineage tree (#457) block: Claude 2.1.259 orchestration replay asserting siblings/children/roots at 15 rows (parallel + nested spawns, stops, the resume), peer resolution/startedAt/subagent against the registry's own facts and against the peer's own requests; Codex 0.147.0 (hook and _meta paths, tree absent for a cold thread); Cursor 3.18.25 (pending child under its subagent_id, bound child, nested child, stops); synthetic two-root Claude and two-workspace Cursor cases for roots; frozen + structuredClone round trip. Two toEqual assertions in lineage-codex-rollout.test.ts gained the tree.
  • examples/worktree-proximity/tests/route-unit/routes.test.ts — journeys 1–8 pass on the new state shape; new: the child sees a sibling's intent while the tree lists it and stops once the tree drops it (the acceptance test), status renders the injected tree, agent/stop releases a child; tests/proximity.test.ts covers the liveness filter and the derived-actor exemption.
  • packages/agent-bundle/tests/worktree-proximity-journeys.test.ts (cross-process) — children are now spawned Claude-style (Agent PreToolUse → SubagentStart → PostToolUse naming the child) and status is called inside the root's hook window: the root sees both children confirmed in the registry-fed tree, still after a server restart (the lineage journal is durable beside the intent state), and only agent-a after agent-b's SubagentStop, which also releases its binding and intent. Exact-revision equality across the restart still holds.

Docs

docs/entry-conventions.md (lineage section: shape and semantics of tree), website/docs/{en,zh}/guide/authoring/mcp.mdx (request-context lineage paragraph). Changeset: .changeset/457-lineage-tree.md (@agent-bundle/runtime patch).

Verification

pnpm typecheck, pnpm lint, pnpm test:unit (3257 passed), pnpm test:route-unit (58), worktree-proximity-journeys.test.ts (integration pool), examples/worktree-proximity check (validate, typecheck, unit, route-unit), examples/host-test typecheck + route-unit, pnpm docs:site:build.

Coordination

Rebased onto main at dacb5b0ff (#536): the proximity route-unit suite was rewritten on top of #525's mountTestState, so this PR's new tests use that harness API too. #532 (request.plugin) touches the same generated shells but not the lineage path and rebases cleanly either way. #459 builds on this PR's tree and is a separate PR; #458 (#539), #460 (#541), #532 (request.plugin), and #466 (#545, canonical.payload on event routes) landed on main while this PR was open and were merged in (6e5936018, 177869456): the proximity example now addresses notices through noticeRecipientFor(binding, worktreeRoot) — a binding whose provenance is derived gets recipient.workspace.root, every other binding recipient.conversation (its actor id is the lineage conversation, so #539's session: prefix stripping is gone with the prefix); status reports #460's notices counts beside agents/bindings/activities; and every event route, including the new agent/stop, reads canonical.payload (agentId/sessionId/toolName/toolInput) instead of the native envelope.

Review status

  • 73bdb9e (PR opened): Codex review completed 2026-09-04T07:20Z with no findings (no review threads).
  • Unreviewed since: 25f6f8ae9 (pins the tree on the two mcp-lineage projection assertions that failed the first CI run) and the rebase onto dacb5b0ff (head 1d22eec96, test file re-applied on mountTestState, no source changes). No PR comments are posted from this side, so no @codex review was requested; the connector does not re-review on its own after a rebase.
  • 6e5936018 and 177869456 (merges of main at 6c2f8da0a and 55026f076): conflict resolution only in examples/worktree-proximity/**, packages/agent-bundle/tests/worktree-proximity-journeys.test.ts, and the two mcp.mdx pages (both sides kept: the tree paragraph followed by Expose a terminal/IO capability to routes and rendered scripts (TTY-ness, color, columns) instead of each plugin re-detecting it #511's terminal section); no packages/rsc-runtime source changes. Unreviewed; verified locally with pnpm typecheck, pnpm lint, examples/worktree-proximity check (6 unit + 16 route-unit), examples/host-test typecheck + route-unit (7), worktree-proximity-journeys.test.ts (integration pool), and pnpm docs:site:build.
  • GitHub Actions created no pull_request runs for the pushes between f2894fc7c and the rebase (close/reopen and a fresh SHA did not help while other PRs' pushes did trigger); the rebased push triggered normally.

@changeset-bot

changeset-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1778694

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@agent-bundle/runtime Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T07:20:39.376794Z 73bdb9e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

ScriptedAlchemy added a commit that referenced this pull request Sep 4, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 4, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@544
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@544
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@544

commit: 1778694

@ScriptedAlchemy
ScriptedAlchemy enabled auto-merge (squash) September 4, 2026 08:45
ScriptedAlchemy and others added 4 commits September 4, 2026 02:12
# Conflicts:
#	examples/worktree-proximity/README.md
#	examples/worktree-proximity/src/event-support.ts
#	examples/worktree-proximity/src/events/session/start.tsx
#	examples/worktree-proximity/src/events/tool/before.tsx
#	examples/worktree-proximity/src/mcp/coordinator/tools/status.tsx
#	examples/worktree-proximity/tests/route-unit/routes.test.ts
#	packages/agent-bundle/tests/worktree-proximity-journeys.test.ts
#	website/docs/en/guide/authoring/mcp.mdx
#	website/docs/zh/guide/authoring/mcp.mdx
# Conflicts:
#	examples/worktree-proximity/src/event-support.ts
#	examples/worktree-proximity/src/events/agent/start.tsx
#	examples/worktree-proximity/src/events/session/start.tsx
#	examples/worktree-proximity/src/events/stop.tsx
#	examples/worktree-proximity/src/events/tool/after.tsx
#	examples/worktree-proximity/src/events/tool/before.tsx
#	examples/worktree-proximity/tests/route-unit/routes.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose the lineage tree (siblings, children, live roots) to routes, not only the request's own chain

1 participant